import json
import logging
import time
import uuid
from dataclasses import asdict

from flask import Blueprint, g, request, abort

from common.exchanges.base import ExchangeErrorCode, ExchangeError
from common.exchanges.contants import ContratType
from common.exchanges.entities import ApiAuthInfo
from common.exchanges.manager import ExchangeServiceManager
from common.models.account import AccountGroup, Account
from common.utils.encrypt import AESEncryptor
from web import settings, db
from web.common.error_code import ErrorCode
from web.common.resp import ok_resp
from web.decorators import login_required
from web.service.account import AccountService
from web.service.trade import TradeService
from web.settings import SUPPORT_BASE_CURRENCIES
from web.executors.multi_leg_trader import MultiLegTradeExecutor

bp = Blueprint("main", __name__)


logger = logging.getLogger('web')


@bp.route('/user/info', methods=['GET'])
@login_required
def user_info():
    resp = ok_resp(g.user.to_dict())
    return resp


@bp.route("/account_group", methods=["POST"])
@login_required
def create_account_group():
    params = json.loads(request.data or '{}')
    name = params.get('name')
    if not name:
        raise ErrorCode.PARAMS_ERROR.to_error('name')

    is_exist = AccountGroup.query.filter_by(user_id=g.user.id, name=name.strip()).count()
    if is_exist:
        raise ErrorCode.ACCOUNT_GROUP_NAME_EXIST.to_error(name)

    account_group = AccountGroup(
        id=str(uuid.uuid4()),
        user_id=g.user.id,
        name=name.strip(),
    )
    db.session.add(account_group)

    for account_info in params.get('accounts', []):
        account = Account(
            id=str(uuid.uuid4()),
            user_id=g.user.id,
            group_id=account_group.id,
            name=account_info.get('name'),
            exchange=account_info.get('exchange'),
            api_key=account_info.get('api_key'),
            api_secret=account_info.get('api_secret'),
        )
        db.session.add(account)
    db.session.commit()
    return ok_resp(account_group.to_dict())


@bp.route("/account", methods=["POST"])
@login_required
def create_account():
    params = json.loads(request.data or '{}')

    group_id = params.get('group_id')
    group = AccountGroup.query.filter_by(id=group_id, user_id=g.user.id).first() if group_id else None

    exchange = params.get('exchange')
    if exchange not in settings.SUPPORT_EXCHANGES:
        raise ErrorCode.PARAMS_ERROR.to_error('exchange')

    if not group:
        raise ErrorCode.PERMISSION_DENIED.to_error()

    api_key = params.get('api_key')
    api_secret = params.get('api_secret')
    account = Account.query.filter_by(user_id=g.user.id, exchange=exchange, group_id=group_id).first()

    if not api_key or not api_secret:
        raise ErrorCode.PARAMS_ERROR.to_error('api key or api secret')

    # TODO account存在时需要判断是否有任务正在执行

    # 交易account权限
    auth_info = ApiAuthInfo(api_key=api_key, api_secret=api_secret)
    service = ExchangeServiceManager.pri_factory(exchange, auth_info, cache=False)
    service.check_auth()

    ExchangeServiceManager.pri_save(exchange, auth_info, service)
    service.ensure_catcher_run()

    api_key = AESEncryptor.encode(api_key, settings.SECRET)
    api_secret = AESEncryptor.encode(api_secret, settings.SECRET)

    if not account:
        account = Account(
            id=str(uuid.uuid4()),
            user_id=g.user.id,
            group_id=group.id,
            name=params.get('name'),
            exchange=exchange,
        )
        db.session.add(account)

    account.api_key = api_key
    account.api_secret = api_secret
    db.session.commit()
    time.sleep(1)
    return ok_resp()


@bp.route("/account_group/summaries", methods=["GET"])
@login_required
def account_group_summaries():
    results = []

    account_groups = AccountGroup.query.filter_by(is_active=True).all()
    for account_group in account_groups:
        group_data = {'name': account_group.name, 'total_equity': 0, 'summaries': [], 'id': account_group.id}
        results.append(group_data)

        accounts = Account.query.filter_by(group_id=account_group.id).all()
        service = AccountService()
        for account in accounts:
            try:
                account_summary = service.get_account_summary(account)
            except Exception as e:
                logger.exception(f'query account summary for {account.id} except {e}')
                account_summary = service.default_account_data(account)

            group_data['total_equity'] += account_summary['equity']
            group_data['summaries'].append(account_summary)

    return ok_resp(results)


@bp.route("/account/position/<account_id>", methods=["GET"])
@login_required
def query_account_position(account_id):
    account = Account.query.filter_by(user_id=g.user.id, id=account_id).first()
    if not account:
        abort(404)
    result = {
        'id': account.id, 'name': account.name, 'exchange': account.exchange, 'details': []
    }
    for currency in SUPPORT_BASE_CURRENCIES:
        detail = {
            'currency': currency,
            'spot': 0,
            'delta': 0,
            'details': []
        }
        result['details'].append(detail)
        positions = []
        for position in positions:
            detail['positions'].append(position.to_dict())

    return ok_resp(result)


@bp.route('/account/open_order/<account_id>', methods=['GET'])
@login_required
def query_account_open_order(account_id):
    account = Account.query.filter_by(user_id=g.user.id, id=account_id).first()
    if not account:
        abort(404)
    return ok_resp([])


@bp.route('/futures', methods=['GET'])
@login_required
def futures_info():
    exchange = request.args.get('exchange')
    currency = request.args.get('currency')
    type_ = request.args.get('type')
    is_reverse = request.args.get('is_reverse')

    if exchange is not None and exchange not in settings.SUPPORT_EXCHANGES:
        ErrorCode.EXCHANGE_NO_SUPPORT.to_error(exchange)

    results = []
    exchanges = [exchange] if exchange else settings.SUPPORT_EXCHANGES
    for exchange in exchanges:
        try:
            service = ExchangeServiceManager.pub_factory(exchange)
            results.extend(service.get_futures_info())
        except Exception as e:
            logger.exception(f'get exchange {exchange} futures info failed: {e}')

    if currency is not None:
        results = [result for result in results if result.base_currency == currency]
    if is_reverse is not None:
        results = [result for result in results if result.reversed == ('1' == is_reverse)]
    if type_ is not None:
        results = [result for result in results if result.future_type == type_]
    resp = []
    for result in results:
        item = asdict(result)
        item['instrument'] = item['instrument_name']
        item['is_reverse'] = item['reversed']
        resp.append(item)
    return ok_resp(resp)


@bp.route('/future/found_rate', methods=['GET'])
@login_required
def future_found_rate():
    exchange = request.args.get('exchange')
    currency = request.args.get('currency')
    type_ = request.args.get('type')
    is_reverse = request.args.get('is_reverse')

    if exchange is not None and exchange not in settings.SUPPORT_EXCHANGES:
        ErrorCode.EXCHANGE_NO_SUPPORT.to_error(exchange)

    results = []
    exchanges = [exchange] if exchange else settings.SUPPORT_EXCHANGES
    for exchange in exchanges:
        try:
            service = ExchangeServiceManager.pub_factory(exchange)
            results.extend(service.get_future_found_rate_details())
        except Exception as e:
            logger.exception(f'get exchange {exchange} futures info failed: {e}')

    if currency is not None:
        results = [result for result in results if result.currency == currency]
    if is_reverse is not None:
        results = [result for result in results if result.reversed == ('1' == is_reverse)]
    if type_ is not None:
        results = [result for result in results if result.future_type == type_]

    return ok_resp([asdict(result) for result in results])


def _get_op_account(exchange, account_id, user_id=None):
    if not account_id:
        raise ErrorCode.PARAMS_ERROR('account')

    account = Account.query.filter_by(exchange=exchange, group_id=account_id).first()
    if not account:
        raise ErrorCode.PARAMS_ERROR('account')
    if user_id is not None and account.user_id != user_id:
        raise ErrorCode.PERMISSION_DENIED.to_error()
    return account


@bp.route('/transaction/balance', methods=['POST'])
@login_required
def create_balance_order():
    params = json.loads(request.data or '{}')
    exchange = params.get('exchange')
    account = _get_op_account(exchange, params.get('account_id'), g.user.id)

    task = TradeService.create_balance_task(g.user, account, params)
    # 保存任务
    db.session.add(task)
    db.session.commit()

    # 开始执行任务
    trader = MultiLegTradeExecutor(task, account=account)
    trader.install()

    return ok_resp({'order_id': task.id})


@bp.route('/transaction/hedge', methods=['POST'])
@login_required
def create_hedge_order():
    params = json.loads(request.data or '{}')
    exchange = params.get('exchange')
    account = _get_op_account(exchange, params.get('account_id'), g.user.id)

    task = TradeService.create_hedge_trade_task(g.user, account, params)
    # 保存任务
    db.session.add(task)
    db.session.commit()

    # 开始执行任务
    trader = MultiLegTradeExecutor(task, account=account)
    trader.install()

    return ok_resp({'order_id': task.id})


@bp.route('/transaction/order/cancel', methods=['POST'])
@login_required
def cancel_order():
    params = json.loads(request.data or '{}')
    exchange = params.get('exchange')
    order_id = params.get('order_id')
    account_type = params.get('account_type')

    if not order_id:
        raise ErrorCode.PARAMS_ERROR('order id or instrument or kind')

    account = _get_op_account(exchange, params.get('account_id'), g.user.id)

    service = ExchangeServiceManager.pri_factory(
        account.exchange, AccountService.account2auth_info(account),
    )

    try:
        order = service.cancel_order(order_id, account_type=account_type)
        logger.info(f'user {g.user.id} cancel {exchange} order {order} success.')
    except ExchangeError as e:
        if e.code in [ExchangeErrorCode.OrderNotExist.code, ExchangeErrorCode.OrderCanceled.code, ExchangeErrorCode.OrderFilled.code]:
            logger.warning(f'user {g.user.id} cancel {exchange} order {order_id} but not exist.')
        else:
            raise e
    return ok_resp()


@bp.route('/account/order/histories', methods=['GET'])
@login_required
def account_histories_order():
    exchange = request.args.get('exchange')
    account_id = request.args.get('account_id')
    account = _get_op_account(exchange, account_id, )

    service = ExchangeServiceManager.pri_factory(
        account.exchange, AccountService.account2auth_info(account), cache=False
    )
    orders = service.open_orders()

    return ok_resp([asdict(order) for order in orders])
